/*
 * Copyright (C) 2016 E.S.R.Labs
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef MINDROID_UTIL_LOGGING_FILEHANDLER_H_
#define MINDROID_UTIL_LOGGING_FILEHANDLER_H_

#include <mindroid/lang/String.h>
#include <mindroid/io/File.h>
#include <mindroid/util/logging/LogHandler.h>
#include <mindroid/util/ArrayList.h>

namespace mindroid {

class ReentrantLock;
class SharedPreferences;

/**
 * A {@code FileHandler} writes logging records into a specified file or a rotating set of files.
 * <p>
 * When a set of files is used and a given amount of data has been written to one file, then this
 * file is closed and another file is opened. The name of these files are generated by given name
 * pattern, see below for details. When the files have all been filled the Handler returns to the
 * first and goes through the set again.
 * <p>
 * By default, the I/O buffering mechanism is enabled, but when each log record is complete, it is
 * flushed out.
 * <p>
 * Name pattern is a string that may include some special substrings, which will be replaced to
 * generate output files:
 * <ul>
 * <li>"/" represents the local pathname separator</li>
 * <li>"%g" represents the generation number to distinguish rotated logs</li>
 * <li>"%h" represents the home directory of the current user, which is specified by "user.home"
 * system property</li>
 * <li>"%t" represents the system's temporary directory</li>
 * <li>"%%" represents the percent sign character '%'</li>
 * </ul>
 * <p>
 * Normally, the generation numbers are not larger than the given file count and follow the sequence
 * 0, 1, 2.... If the file count is larger than one, but the generation field("%g") has not been
 * specified in the pattern, then the generation number after a dot will be added to the end of the
 * file name.
 */
class FileHandler : public LogHandler {
public:
    FileHandler();

    void init(const sp<String>& pattern, bool append, int32_t limit, int32_t count, int32_t bufferSize, int32_t dataVolumeLimit);
    void initProperties(const sp<String>& p, bool a, int32_t l, int32_t c, int32_t bufferSize, int32_t dataVolumeLimit);
    void initOutputFiles();
    void initPreferences();
    void findNextGeneration();

    /**
     * Transform the pattern to the valid file name, replacing any patterns, and applying generation
     * if present.
     *
     * @param gen Generation of this file.
     * @return Transformed filename ready for use.
     */
    sp<String> parseFileName(uint32_t gen);

    /**
     * Constructs a new {@code FileHandler}. The given name pattern is used as output filename, the
     * file limit is set to zero (no limit), the file count is set to one. This handler writes to
     * only one file with no size limit.
     *
     * @param pattern The name pattern for the output file.
     * @throws IOException if any I/O error occurs.
     * @throws IllegalArgumentException if the pattern is empty.
     * @throws NullPointerException if the pattern is {@code null}.
     */
    FileHandler(const sp<String>& pattern);

    /**
     * Construct a new {@code FileHandler}. The given name pattern is used as output filename, the
     * file limit is set to zero (no limit), the file count is initialized to one and the value of
     * {@code append} becomes the new instance's append mode. This handler writes to only one file
     * with no size limit.
     *
     * @param pattern The name pattern for the output file.
     * @param append The append mode.
     * @throws IOException if any I/O error occurs.
     * @throws IllegalArgumentException if {@code pattern} is empty.
     * @throws NullPointerException if {@code pattern} is {@code null}.
     */
    FileHandler(const sp<String>& pattern, bool append);

    /**
     * Construct a new {@code FileHandler}. The given name pattern is used as output filename, the
     * maximum file size is set to {@code limit} and the file count is initialized to {@code count}.
     * This handler is configured to write to a rotating set of count files, when the limit of bytes
     * has been written to one output file, another file will be opened instead.
     *
     * @param pattern The name pattern for the output file.
     * @param limit The data amount limit in bytes of one output file, can not be negative.
     * @param count The maximum number of files to use, can not be less than one.
     * @throws IOException if any I/O error occurs.
     * @throws IllegalArgumentException if {@code pattern} is empty, {@code limit < 0} or
     * {@code count < 1}.
     * @throws NullPointerException if {@code pattern} is {@code null}.
     */
    FileHandler(const sp<String>& pattern, int32_t limit, int32_t count);

    /**
     * Construct a new {@code FileHandler}. The given name pattern is used as output filename, the
     * maximum file size is set to {@code limit}, the file count is initialized to {@code count} and
     * the append mode is set to {@code append}. This handler is configured to write to a rotating
     * set of count files, when the limit of bytes has been written to one output file, another file
     * will be opened instead.
     *
     * @param pattern The name pattern for the output file.
     * @param limit The data amount limit in bytes of one output file, can not be negative.
     * @param count The maximum number of files to use, can not be less than one.
     * @param append The append mode.
     * @throws IOException if any I/O error occurs.
     * @throws IllegalArgumentException if {@code pattern} is empty, {@code limit < 0} or
     * {@code count < 1}.
     * @throws NullPointerException if {@code pattern} is {@code null}.
     */
    FileHandler(const sp<String>& pattern, int32_t limit, int32_t count, bool append);

    FileHandler(const sp<String>& pattern, int32_t limit, int32_t count, bool append, int32_t bufferSize, int32_t dataVolumeLimit);

    void clear();

    /**
     * Flushes and closes all opened files.
     */
    void close() override;

    void flush() override;

    /**
     * Publish a {@code LogRecord}.
     *
     * @param record The log record.
     */
    void publish(const sp<LogBuffer::LogRecord>& record) override;

    bool dump(const sp<String>& fileName);

    class Writer : public Object {
    public:
        Writer(const sp<File>& file, bool append);
        virtual ~Writer();

        void write(const sp<String>& s);
        void write(const sp<String>& s, uint32_t offset, size_t length);
        void flush();
        void close();

        int32_t size();
        void newLine();

    private:
        int mFd = -1;
        int64_t mSize;
    };

private:
    static const int32_t DEFAULT_COUNT = 1;
    static const int32_t DEFAULT_LIMIT = 0;
    static const bool DEFAULT_APPEND = false;
    static const sp<String> DEFAULT_PATTERN;
    static const sp<String> CRLF;
    static const sp<String> DATA_VOLUME;

    sp<String> mPattern;
    bool mAppend = DEFAULT_APPEND;
    int32_t mLimit = DEFAULT_LIMIT;
    int32_t mCount = DEFAULT_COUNT;
    sp<Writer> mWriter;
    sp<ArrayList<sp<File>>> mFiles = new ArrayList<sp<File>>();
    sp<String> mFileName;
    int32_t mBufferSize = 0;
    int32_t mFlushSize = 0;
    int32_t mDataVolume = 0;
    int32_t mDataVolumeLimit = 0;
    sp<ReentrantLock> mLock;
    sp<SharedPreferences> mPreferences;
};

} /* namespace mindroid */

#endif /* MINDROID_UTIL_LOGGING_FILEHANDLER_H_ */
